﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Tools.MAME;
using System.ComponentModel;
using System.IO;

namespace GameListEditor
{
    ///-------------------------------------------------------------------------------------------------
    /// <summary> Database object list. </summary>
    ///-------------------------------------------------------------------------------------------------
    public partial class DbObjectList : UserControl
    {
        #region Member Variables

        private Stack<DbItem> m_undoStack = new Stack<DbItem>(99);
        private bool m_showSnapShots = false;
        private String m_snapShotDirectory = null;

        #endregion

        #region Standard Properties

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets a value indicating whether to show snap shots or not. </summary>
        /// <value> True to show snap shots, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        [DefaultValue(false)]
        public bool ShowSnapShots
        {
            get { return m_showSnapShots; }
            set
            {
                m_showSnapShots = value;
                UpdateSnapShotPanelVisibility();
                UpdateSnapShot();
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets the pathname of the snap shots directory. </summary>
        /// <value> The pathname of the snap shots directory. </value>
        ///-------------------------------------------------------------------------------------------------
        [DefaultValue(null)]
        public String SnapShotsDirectory
        {
            get { return m_snapShotDirectory; }
            set
            {
                m_snapShotDirectory = value;
                UpdateSnapShot();
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the number of items in the list. </summary>
        /// <value> The number of items in the list. </value>
        ///-------------------------------------------------------------------------------------------------
        public int Count { get { return objectList.Items.Count; } }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the number of selected items. </summary>
        /// <value> The number of selected items. </value>
        ///-------------------------------------------------------------------------------------------------
        public int SelectedItemCount { get { return objectList.SelectedItems.Count; } }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the selected game's name. </summary>
        /// <value> The name of the selected game. </value>
        ///-------------------------------------------------------------------------------------------------
        public String SelectedGameName { get { return objectList.SelectedItems[0].SubItems[EditorConst.NAME_COLUMN_INDEX].Text; } }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the selected game's cloneof attribute. </summary>
        /// <value> The cloenof attribute of the selected game. </value>
        ///-------------------------------------------------------------------------------------------------
        public String SelectedCloneOf { get { return objectList.SelectedItems[0].SubItems[EditorConst.CLONE_OF_COLUMN_INDEX].Text; } }

        #endregion

        #region Auto-Properties

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets a value indicating whether to automatically size snap shots. </summary>
        /// <value> True to automatically size snap shots, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        [DefaultValue(true)]
        public bool AutoSizeSnapShots { get; set; }

        #endregion

        #region Events

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event queue for all listeners interested in SelectedIndexChanged events. </summary>
        ///-------------------------------------------------------------------------------------------------
        public event EventHandler<EventArgs> SelectedIndexChanged;

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Raises the selected index changed event. </summary>
        /// <param name="e"> Event information to send to registered event handlers. </param>
        ///-------------------------------------------------------------------------------------------------
        private void OnSelectedIndexChanged(EventArgs e)
        {
            if (SelectedIndexChanged != null)
                SelectedIndexChanged(this, e);
        }

        #endregion

        #region Constructor(s)

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Default constructor. </summary>
        ///-------------------------------------------------------------------------------------------------
        public DbObjectList()
        {
            // Assign default values to auto-properties.
            AutoSizeSnapShots = true;

            InitializeComponent();

            treeColName.GroupKeyGetter = delegate(object x)
            {
                return ((DbItem)x).Group;
            };
        }

        #endregion

        #region General Public

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Removes all items from the list. </summary>
        ///-------------------------------------------------------------------------------------------------
        public void Clear()
        {
            if (objectList.InvokeRequired)
                objectList.Invoke(new MethodInvoker(delegate { Clear(); }));
            else
            {
                try
                {
                    ClearUndoStack();

                    objectList.BeginUpdate();

                    try
                    {
                        objectList.Items.Clear();
                    }
                    finally
                    {
                        objectList.EndUpdate();
                    }
                }
                catch
                {
                    // TODO: Handle error.
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Clears list and adds items. </summary>
        /// <exception cref="ArgumentNullException"> Thrown when one or more required arguments are null. </exception>
        /// <param name="items"> The items to add. </param>
        ///-------------------------------------------------------------------------------------------------
        public void ClearAndAdd(List<DbItem> items)
        {
            if (objectList.InvokeRequired)
                objectList.Invoke(new MethodInvoker(delegate { ClearAndAdd(items); }));
            else
            {
                try
                {
                    if (items == null)
                        throw new ArgumentNullException("items");

                    ClearUndoStack();

                    objectList.BeginUpdate();

                    try
                    {
                        objectList.SetObjects(items);

                        // Autosize headers.
                        objectList.AutoResizeColumns();
                    }
                    finally
                    {
                        objectList.EndUpdate();
                    }
                }
                catch
                {
                    // TODO: Handle error.
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds items. </summary>
        /// <exception cref="ArgumentNullException"> Thrown when one or more required arguments are null. </exception>
        /// <param name="items"> The items to add. </param>
        ///-------------------------------------------------------------------------------------------------
        public void Add(List<DbItem> items)
        {
            if (objectList.InvokeRequired)
                objectList.Invoke(new MethodInvoker(delegate { Add(items); }));
            else
            {
                try
                {
                    if (items == null)
                        throw new ArgumentNullException("items");


                    ClearUndoStack();

                    objectList.BeginUpdate();

                    try
                    {
                        foreach (DbItem item in items)
                        {
                            bool found = false;
                            foreach (ListViewItem lvItem in objectList.Items)
                            {
                                if (lvItem.SubItems[EditorConst.NAME_COLUMN_INDEX].Text.Equals(item.Name, StringComparison.OrdinalIgnoreCase))
                                {
                                    found = true;
                                    break;
                                }
                            } // End for each list view item.

                            if (!found)
                                objectList.AddObject(item);
                        } // End for each db item.

                        // Autosize headers.
                        objectList.AutoResizeColumns();
                    }
                    finally
                    {
                        objectList.EndUpdate();
                    }
                }
                catch
                {
                    // TODO: Handle error.
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Deletes the selected. </summary>
        ///-------------------------------------------------------------------------------------------------
        public void DeleteSelected()
        {
            if (objectList.InvokeRequired)
                objectList.Invoke(new MethodInvoker(delegate { DeleteSelected(); }));
            else
            {
                if (objectList.SelectedItems.Count > 0)
                {
                    // BUild list of selected objects.
                    List<DbItem> selectedItems = new List<DbItem>();
                    foreach (object obj in objectList.SelectedObjects)
                        selectedItems.Add(obj as DbItem);

                    objectList.BeginUpdate();

                    try
                    {
                        foreach (DbItem item in selectedItems)
                        {
                            // Add to undo stack.
                            m_undoStack.Push(item); // TODO: What happens at capacity?
                            objectList.RemoveObject(item);
                        }

                        // TODO: Need to get post-delete selection working.

                        //if (objectList.SelectedItem != null)
                        //    objectList.FocusedItem = objectList.SelectedItem;
                    }
                    finally
                    {
                        objectList.EndUpdate();
                    }
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the inner list view items. </summary>
        /// <value> The list view items. </value>
        ///-------------------------------------------------------------------------------------------------
        public ListView.ListViewItemCollection ListViewItems { get { return objectList.Items; } }

        #endregion

        #region Undo/Redo

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets a value indicating whether undo is available. </summary>
        /// <value> True if we can undo, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        public bool CanUndo { get { return m_undoStack.Count > 0; } }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Performs undo of last action. </summary>
        ///-------------------------------------------------------------------------------------------------
        public void Undo()
        {
            // TODO: Works, but incredibly slow.
            if (m_undoStack.Count > 0)
                objectList.AddObject(m_undoStack.Pop());
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Clears the undo stack. </summary>
        ///-------------------------------------------------------------------------------------------------
        public void ClearUndoStack() { m_undoStack.Clear(); }

        #endregion

        #region Snap Shots

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Assign snap shot properties. </summary>
        /// <param name="showSnapShots">     True to show snap shots, false if not. </param>
        /// <param name="autoSizeSnapShots"> True to automatically size snap shots, false if not. </param>
        /// <param name="snapShotDirectory"> Pathname of the snap shot directory. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AssignSnapShotProperties(bool showSnapShots, bool autoSizeSnapShots, String snapShotDirectory)
        {
            m_showSnapShots = showSnapShots;
            m_snapShotDirectory = snapShotDirectory;
            
            AutoSizeSnapShots = autoSizeSnapShots;

            UpdateSnapShotPanelVisibility();
            UpdateSnapShot();
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Update visibility of the snap shot panel based on current setting. </summary>
        ///-------------------------------------------------------------------------------------------------
        private void UpdateSnapShotPanelVisibility()
        {
            if (splitContainer.InvokeRequired)
                splitContainer.BeginInvoke(new MethodInvoker(delegate { UpdateSnapShotPanelVisibility(); }));
            else
            {
                try
                {
                    splitContainer.Panel2Collapsed = !ShowSnapShots;
                }
                catch
                { }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Update current snap shot. </summary>
        ///-------------------------------------------------------------------------------------------------
        private void UpdateSnapShot()
        {
            if (pictureBox.InvokeRequired)
                pictureBox.BeginInvoke(new MethodInvoker(delegate { UpdateSnapShot(); }));
            else
            {
                try
                {
                    if (ShowSnapShots && (!String.IsNullOrWhiteSpace(SnapShotsDirectory)))
                    {
                        if (Count > 0)
                        {
                            String fileName = Path.Combine(SnapShotsDirectory, SelectedGameName) + ".png";
                            if (File.Exists(fileName))
                                pictureBox.ImageLocation = fileName;
                            else
                            {
                                String cloneOf = SelectedCloneOf;
                                if (!String.IsNullOrWhiteSpace(cloneOf))
                                {
                                    fileName = Path.Combine(SnapShotsDirectory, cloneOf) + ".png";
                                    if (File.Exists(fileName))
                                        pictureBox.ImageLocation = fileName;
                                    else
                                        pictureBox.ImageLocation = null;
                                }
                                else
                                    pictureBox.ImageLocation = null;
                            }
                        }
                    }
                }
                catch
                { }
            }
        }

        #endregion

        #region Control Event Handlers

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by objectList for selected index changed events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void objectList_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                UpdateSnapShot();
            }
            catch
            { }

            OnSelectedIndexChanged(e);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by pictureBox for size changed events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void pictureBox_SizeChanged(object sender, EventArgs e)
        {
            if (AutoSizeSnapShots)
            {
                splitContainer.SplitterDistance = Math.Max(splitContainer.Panel1MinSize,
                    splitContainer.Width - (pictureBox.Width + splitContainer.SplitterWidth));
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by objectList for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void objectList_Click(object sender, EventArgs e)
        {
            OnClick(e);
        }

        #endregion                
    }
}
